﻿using System;
using System.IO;
using NUnit.Framework;

namespace Duhking.Tests
{
    [TestFixture]
    public class InterfaceDuhkerTests
    {
        public interface IPropertyOnly
        {
            string Name { get; set; }
            string Id { get; }
        }

        public interface INestedProperties
        {
            string Name { get; set; }
            string Id { get; }
            IBasicMethodInvoke Nested { get; set; }
        }

        public interface ICirularReferenceLevel1
        {
            string Name { get; set; }
            ICircularReferenceLevel2 Level2 { get; set; }
        }

        public interface ICircularReferenceLevel2
        {
            ICirularReferenceLevel1 Level1 { get; set; }
        }

        public interface IBasicMethodInvoke
        {
            string DoSomething(string arg1);
        }

        public class TestBasicMethodInvoke
        {
            public bool MethodCalled { get; set; }
            public string Arg { get; set; }

            public string DoSomething(string arg)
            {
                Arg = arg;
                MethodCalled = true;
                return arg;
            }

        }

        private class TestDisposableSignatures
        {
            public bool HasDisposed { get; set; }
            public void Dispose()
            {
                HasDisposed = true;
            }
        }

        private class NotQuiteDisposableSignature
        {
            public void Dispose(bool disposing)
            {}
        }

        private class StillNotDisposableSignature
        {
            public bool Dispose()
            {
                return false;
            }
        }

        public class PropertyOnlyImplementor
        {
            public string Name { get; set; }
            public string Id { get; private set; }
        }

        public class MissingPropertyImplementor
        {
            public string Name { get; set; }
        }

        public class IncorrectVisibilityImplementor
        {
            public string Name { get; set; }
            public string Id { private get; set; }
        }

        public class NestedPropertyImplementor
        {
            public string Name { get; set; }
            public string Id { get; set; }
            public TestBasicMethodInvoke Nested { get; set; }

            public NestedPropertyImplementor()
            {
                Nested = new TestBasicMethodInvoke();
            }
        }

        public class CicularReferenceLevel1Implementor
        {

            public string Name { get; set; }
            public CircularReferenceLevel2Implementer Level2 { get; set; }

        }

        public class CircularReferenceLevel2Implementer
        {
            public CicularReferenceLevel1Implementor Level1 { get; set; }
        }

        private InterfaceDuhker _duhker;

        [SetUp]
        public void Setup()
        {
            _duhker = new InterfaceDuhker();
        }

        [Test]
        public void CanDuhk_Throws_IfTypeArgumentIsNotAnInterface()
        {
            Assert.That(() => _duhker.CanDuhk<Stream>(new object()), Throws.TypeOf<InvalidOperationException>().With.Message.EqualTo("Unable to Duhk type System.IO.Stream because it is not an interface.  Duhking of non-interface types is currently not supported"));
        }
        
        [Test]
        public void CanDuhk_ReturnsFalse_IfObjectDoesNotMatchingMethodSignaturesForAllInterfaceMethods()
        {
            Assert.That(_duhker.CanDuhk<IDisposable>(new object()), Is.False);
        }

        [Test]
        public void CanDuhk_ReturnsTrue_IfObjectImplementsAllMethodsFromInterface()
        {
            Assert.That(_duhker.CanDuhk<IDisposable>(new TestDisposableSignatures()), Is.True);            
        }

        [Test]
        public void CanDuhk_ReturnsFalse_If_Method_Parameter_Counts_Differ()
        {
            Assert.That(_duhker.CanDuhk<IDisposable>(new NotQuiteDisposableSignature()), Is.False);
        }

        [Test]
        public void CanDuhk_ReturnsFalse_If_Method_Return_Value_Differs()
        {
            Assert.That(_duhker.CanDuhk<IDisposable>(new StillNotDisposableSignature()), Is.False);
        }

        [Test]
        public void CanDuhk_ReturnsFalse_If_Object_Does_Not_Have_Matching_Properties()
        {
            Assert.That(_duhker.CanDuhk<IPropertyOnly>(new MissingPropertyImplementor()), Is.False);
        }

        [Test]
        public void CanDuhk_ReturnsFalse_If_Object_Does_Not_Have_Correct_Visibility_For_GettersSetters()
        {
            Assert.That(_duhker.CanDuhk<IPropertyOnly>(new IncorrectVisibilityImplementor()), Is.False);
        }

        [Test]
        public void CanDuhk_ReturnsTrue_If_Object_Matches_All_Properties()
        {
            Assert.That(_duhker.CanDuhk<IPropertyOnly>(new PropertyOnlyImplementor()), Is.True);
        }

        [Test]
        public void CanDuhk_ReturnsTrue_If_Object_Properties_Are_Duhkable()
        {
            Assert.That(_duhker.CanDuhk<INestedProperties>(new NestedPropertyImplementor()), Is.True);
        }

        [Test]
        public void Duhk_Returns_A_Proxy_That_Calls_Through_To_Target_Object()
        {
            var testDisposable = new TestBasicMethodInvoke();
            var duhk = _duhker.Duhk<IBasicMethodInvoke>(testDisposable);
            var result = duhk.DoSomething("HA!");
            Assert.That(result, Is.EqualTo("HA!"));
            Assert.That(testDisposable.MethodCalled, Is.True);
        }

        [Test]
        public void Duhk_Returns_A_Proxy_That_Accesses_Properties()
        {
            var testClass = new PropertyOnlyImplementor();
            var interfaceInstance = _duhker.Duhk<IPropertyOnly>(testClass);

            testClass.Name = "Something";
            Assert.AreEqual(interfaceInstance.Name, "Something");
        }

        [Test]
        public void Duhk_Throws_If_CanDuhk_Returns_False()
        {
            var testClass = new PropertyOnlyImplementor();
            Assert.That(() => _duhker.Duhk<IDisposable>(testClass), Throws.TypeOf<InvalidOperationException>().With.Message.EqualTo("Unable to Duhk type System.IDisposable using concrete type PropertyOnlyImplementor"));
        }

        [Test]
        public void Duhk_Returns_A_Proxy_With_Nested_Duhks()
        {
            var testClass = new NestedPropertyImplementor();
            var duhkedInstance = _duhker.Duhk<INestedProperties>(testClass);

            Assert.That(duhkedInstance.Nested, Is.Not.Null);
            duhkedInstance.Nested.DoSomething("test");

        }

        [Test]
        public void Duhk_Handles_Circular_References()
        {
            var testClass = new CicularReferenceLevel1Implementor { Name = "Test Circle", Level2 = new CircularReferenceLevel2Implementer() };
            testClass.Level2.Level1 = testClass;

            var duhkedInstance = _duhker.Duhk<ICirularReferenceLevel1>(testClass);

            Assert.That(duhkedInstance.Level2, Is.Not.Null);
            Assert.That(duhkedInstance.Level2.Level1, Is.SameAs(duhkedInstance));
        }
    }
}
